/*
According to apache license

This is fork of christocracy cordova-plugin-background-geolocation plugin
https://github.com/christocracy/cordova-plugin-background-geolocation

This is a new class
*/

package com.marianhello.bgloc;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

import com.marianhello.bgloc.data.BackgroundLocation;
import com.marianhello.bgloc.data.DAOFactory;
import com.marianhello.bgloc.data.LocationDAO;
import com.marianhello.bgloc.data.LocationPackage;
import com.marianhello.bgloc.data.LocationPackageDAO;
import com.marianhello.bgloc.HttpPostService;
import com.tenforwardconsulting.cordova.BackgroundGeolocationPlugin;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;

import io.ionic.starter.R;

public class LocationService extends Service {
    private static final String TAG = "LocationService";
    /**  Automatic start and stop */
    public static boolean autoStartStop = true;
    /** Trip Id */
    public static int tripId = 0;
    /** Current Trip exists */
    public static boolean onTrip = false;
    public static int satellitesCount = 0;
    private LocationDAO dao = null;
    private LocationPackageDAO lpDao = null;
    private Config config;
    private LocationProvider provider = null;

    /** Keeps track of all current registered clients. */
    ArrayList<Messenger> mClients = new ArrayList<Messenger>();

    /** Trip status for clients.
     *  serviceStartId:
     * 	serviceStartTime:
     *  tripDurationTime:
     *  tripResponse:
     */
    private JSONObject mStatus = new JSONObject();
    private AlarmManager mAlarmManager = null;
    private PendingIntent mDBLoopPI = null;

    private int mPackageNum = 0;
    private int mMovementStatus = 0;    // 0: Unknown 1: Stationary 2: Moving 3: Driving
    private long mTripStartTime = 0L;
    private Integer mLastGpsSatelliteCount = 0;

    private static final String P_NAME = "com.marianhello.bgloc.LocationService";
    private static final String DB_LOCATION_PACKAGE_LOOPING_ACTION = P_NAME + ".DB_LOCATION_PACKAGE_LOOPING_ACTION";

    private static final String LOCATION_PACKAGE_COMPRESSION_METHOD_LZ64 = "lz64";
    // Debug
//    private static final int LOCATION_PACKAGE_SIZE = 1;
    private static final int DB_MAX_ROW = 500;
    private static final long DB_LOCATION_PACKAGE_POLLING_INTERVAL         = 10 * 1000 * 60;    // 5 minutes.

    private ArrayList<Object> mLocationPackage = new ArrayList<Object>();

    /**
     * Command to the service to register a client, receiving callbacks
     * from the service.  The Message's replyTo field must be a Messenger of
     * the client where callbacks should be sent.
     */
    public static final int MSG_REGISTER_CLIENT = 1;

    /**
     * Command to the service to unregister a client, ot stop receiving callbacks
     * from the service.  The Message's replyTo field must be a Messenger of
     * the client as previously given with MSG_REGISTER_CLIENT.
     */
    public static final int MSG_UNREGISTER_CLIENT = 2;

    /**
     * Command sent by the service to
     * any registered clients with the new trip status update.
     */
    public static final int MSG_STATUS_UPDATE = 3;

    /**
     * Command to the service to
     * start/continue a trip with params.
     */
    public static final int MSG_START_TRIP = 4;

    /**
     * Command to the service to
     * stop a trip.
     */
    public static final int MSG_STOP_TRIP = 5;

    /**
     * Command sent by the service to
     * client with the result of start trip.
     */
    public static final int MSG_START_TRIP_RESPONSE = 6;

    /**
     * Command sent by the service to
     * client with the result of stop trip.
     */
    public static final int MSG_STOP_TRIP_RESPONSE = 7;

    /**
     * Command to the service to
     * launch the transfer.
     */
    public static final int MSG_TRANSFER_LOCATIONS = 8;

    /**
     * Command to the service to
     * stop the transfer.
     */
    public static final int MSG_STOP_TRANSFER = 9;

    /**
     * Command sent by the service to
     * any registered clients with the GPS status update.
     */
    public static final int MSG_GPS_STATUS_UPDATE = 10;

    /**
     * Command sent by the service to
     * any registered clients with the network status update.
     */
    public static final int MSG_NETWORK_STATUS_UPDATE = 11;
    /** Auto Start and Stop */
    public static final int MSG_AUTO_START_STOP_OPEN = 12;
    public static final int MSG_AUTO_START_STOP_CLOSE = 13;
    public static final int MSG_STATUS = 14;

    public static final int NETWORK_ERROR = 30;

    public static final String KEY_TRIP_DURATION = "tripDurationTime";
    public static final String KEY_TRIP_RESPONSE = "tripResponse";
    /**
     * Handler of incoming messages from clients.
     */

    private volatile HandlerThread loopThread;
    private DBLoopHandler loopHandler;
  private SensorMonitor sensorMonitor;

    private class DBLoopHandler extends Handler {
        public DBLoopHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
        }
    }

    private BroadcastReceiver DBLoopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if ( isNetworkAvailable() ) {
                runDBTransfer();
            } else {
                notifyNetworkIssue();
            }

        }
    };

    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            	// Store client and reply the current status
                case MSG_REGISTER_CLIENT:
                    mClients.add(msg.replyTo);
                    if (mTripStartTime != 0L) {
                        try {
                            Message replyMsg =  Message.obtain(null, MSG_STATUS_UPDATE);
                            long duration = System.currentTimeMillis() - mTripStartTime;
                            mStatus.put(KEY_TRIP_DURATION, duration);
                            Bundle bundle  = new Bundle();
                            bundle.putString("msg",  mStatus.toString());
                            replyMsg.setData(bundle);
                            msg.replyTo.send(replyMsg);
                        } catch (RemoteException e) {
                            Log.e(TAG, e.toString());
                        } catch (JSONException e) {
                            Log.e(TAG, e.toString());
					    }
                    }
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    break;
                case MSG_START_TRIP:
                    String strStartBundle = msg.getData().getString("msg");
                    PostStartTripTask startTripTask = new LocationService.PostStartTripTask();
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                    	// startTripTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, strStartBundle);
                        startTripTask.execute(strStartBundle);
                    }
                    else {
                    	startTripTask.execute(strStartBundle);
                    }
                    break;
                case MSG_STOP_TRIP:
                    // Try to transfer the cache at the end of the trip
                    // runDBTransfer();
                  String strStopBundle = msg.getData().getString("msg");
                	PostStopTripTask stopTripTask = new LocationService.PostStopTripTask();
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                      stopTripTask.execute(strStopBundle);
                        // stopTripTask.execute((ArrayList<Object>) mLocationPackage.clone(), ++mPackageNum);
                    }
                    else {
                    	stopTripTask.execute(strStopBundle);
                    }
                    break;
                case MSG_TRANSFER_LOCATIONS:
                  tripId =  msg.getData().getInt("tripId");
                	startTransferLocations();
                	break;
                case MSG_STOP_TRANSFER:
                	stopTransferLocations();
                	break;
                case MSG_AUTO_START_STOP_OPEN:
                  autoStartStop = true;
                  break;
                case MSG_AUTO_START_STOP_CLOSE:
                  autoStartStop = false;
                	break;
                case MSG_STATUS:
                    try {
                        Message replyMsg =  Message.obtain(null, MSG_STATUS);
                        JSONObject onTrip = new JSONObject();
                        onTrip.put("onTrip", LocationService.onTrip);
                        Bundle bundle  = new Bundle();
                        bundle.putString("msg", onTrip.toString() );
                        replyMsg.setData(bundle);
                        msg.replyTo.send(replyMsg);
                    } catch (RemoteException e) {
                        Log.e(TAG, e.toString());
                    } catch (JSONException e) {
                        Log.e(TAG, e.toString());
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (dao == null)
            dao = (DAOFactory.createLocationDAO(this));

        if (lpDao == null) {
            lpDao = (DAOFactory.createLocationPackageDAO(this));
        }

        if  (loopThread == null) {
            loopThread = new HandlerThread("LocationService.LoopThread");
            loopThread.start();

            loopHandler = new DBLoopHandler(loopThread.getLooper());
        }
        if (sensorMonitor == null) {
          sensorMonitor = new SensorMonitor(LocationService.this);
        }
    }

    @Override
    public void onDestroy() {
        Log.w(TAG, "Destroying Location Service");
        provider.onDestroy();

        stopTransferLocations();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            loopThread.quitSafely();
        } else {
            loopThread.quit(); //sorry
        }

        stopDBLoop();
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        stopForeground(Service.STOP_FOREGROUND_REMOVE);
      }
      super.onDestroy();
    }

    // @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Log.d(TAG, "Task has been removed");
        if (config.getStopOnTerminate()) {
            Log.d(TAG, "Stopping self");
            stopSelf();
        } else {
            Log.d(TAG, "Continue running in background");
//            Intent intent = new Intent( this, DummyActivity.class );
//            intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
//            startActivity(intent);
        }
        super.onTaskRemoved(rootIntent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Received start id " + startId + ": " + intent);
        startForeground(startId, new Notification());
        if (provider != null) {
            return START_STICKY;
        }

        if (intent.hasExtra("config")) {
            config = (Config) intent.getParcelableExtra("config");
        } else {
            config = new Config();
        }
        Log.i(TAG, "Config " + config);
        try {
        	mStatus.put("serviceStartId", startId);
			mStatus.put("serviceStartTime", System.currentTimeMillis());
		} catch (JSONException e) {
			Log.e(TAG, e.toString());
		}
        Log.i(TAG, "mStatus " + mStatus);
        LocationProviderFactory spf = new LocationProviderFactory(this);
        provider = spf.getInstance(config.getLocationProvider());

        //We want this service to continue running until it is explicitly stopped
        //return START_REDELIVER_INTENT;
        Log.i(TAG, "provider " + provider);
        return START_STICKY;
    }

    protected Integer getPluginResource(String resourceName) {
        return getApplication().getResources().getIdentifier(resourceName, "drawable", getApplication().getPackageName());
    }

    /**
     * Adds an onclick handler to the notification
     */
    protected NotificationCompat.Builder setClickEvent (NotificationCompat.Builder builder) {
        int requestCode = new Random().nextInt();
        Context context     = getApplicationContext();
        String packageName  = context.getPackageName();
        Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
        launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent contentIntent = PendingIntent.getActivity(context, requestCode, launchIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        return builder.setContentIntent(contentIntent);
    }

    private Integer parseNotificationIconColor(String color) {
        int iconColor = 0;
        if (color != null) {
            try {
                iconColor = Color.parseColor(color);
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "couldn't parse color from android options");
            }
        }
        return iconColor;
    }

    public void startTransferLocations() {
        if ( mTripStartTime != 0L )
            return;
    	mTripStartTime = System.currentTimeMillis();
    	mPackageNum = 0;
        mLastGpsSatelliteCount = 0;
      satellitesCount = 0;
    	mLocationPackage.clear();
    	configNotification(false, null, null);
    	startRecording();
        clearDB();
        startDBLoop();
    }

    public void stopTransferLocations() {
        if ( mTripStartTime == 0L )
            return;
        configNotification(true, null, null);
        stopRecording();
        stopDBLoop();
        clearDB();
    	mTripStartTime = 0L;
    	mPackageNum = 0;
        mLastGpsSatelliteCount = 0;
      satellitesCount = 0;
    	mLocationPackage.clear();
    	mStatus.remove(KEY_TRIP_DURATION);
    	mStatus.remove(KEY_TRIP_RESPONSE);
    }

    public void startRecording() {
        provider.startRecording();
    }

    public void stopRecording() {
        provider.stopRecording();
    }

    protected void startDBLoop() {
        mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        mDBLoopPI = PendingIntent.getBroadcast(this, 0, new Intent(DB_LOCATION_PACKAGE_LOOPING_ACTION), 0);
        registerReceiver(DBLoopReceiver, new IntentFilter(DB_LOCATION_PACKAGE_LOOPING_ACTION), null, loopHandler);
        mAlarmManager.cancel(mDBLoopPI);
        mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + DB_LOCATION_PACKAGE_POLLING_INTERVAL, DB_LOCATION_PACKAGE_POLLING_INTERVAL, mDBLoopPI);
    }

    protected void stopDBLoop() {
        if (mAlarmManager != null) {
            mAlarmManager.cancel(mDBLoopPI);
            unregisterReceiver(DBLoopReceiver);
            mAlarmManager = null;
        }
    }

    protected void clearDB() {
        if (lpDao != null) {
            lpDao.clear();
        }
    }

    protected void runDBTransfer() {
        if (lpDao == null)
            return;

    	Collection<LocationPackage> lps = lpDao.getAll();
    	Log.w(TAG, "###DB size: " + String.valueOf(lps.size()));
        for (LocationPackage lp : lps) {
        	String response = postLocationPackageAgain(lp.getPackagePayload(), lp.getPackageNum(), lp.getId());
            // Looping packages will be removed from DB if post successfully, with no communication to clients
            if (response != null) {
                lpDao.delete(lp.getId());
            } else {
                break;
            }
       	}
    }

    public void handleLocation (BackgroundLocation location) {
        // Boolean shouldPersists = mClients.size() == 0;

//        for (int i = mClients.size() - 1; i >= 0; i--) {
//            try {
//                Bundle bundle = new Bundle();
//                bundle.putParcelable("location", location);
//                Message msg = Message.obtain(null, MSG_LOCATION_UPDATE);
//                msg.setData(bundle);
//                mClients.get(i).send(msg);
//            } catch (RemoteException e) {
//                // The client is dead.  Remove it from the list;
//                // we are going through the list from back to front
//                // so this is safe to do inside the loop.
//                mClients.remove(i);
//                // shouldPersists = true;
//            }
//        }
    	mLocationPackage.add(location.toArray());
    	// Log.e(TAG, "###handle location in!");
    	if (mLocationPackage.size() >= config.getDesiredAccuracy()) {
            if (config.getUrl() != null && !config.getUrl().isEmpty()) {
                if ( !isNetworkAvailable() ) {
                    notifyNetworkIssue();
                }
                postLocationPackage( mLocationPackage.clone(), ++mPackageNum);
              mLocationPackage.clear();
            }
    	}

//        if (config.isDebugging()) {
//            BackgroundLocation cloned = location.makeClone();
//            cloned.setDebug(true);
//            persistLocation(cloned);
//        }

        // if (shouldPersists) {
        //     Log.d(TAG, "Persisting location. Reason: Main activity was probably killed.");
        //     persistLocation(location);
        // }
    }

    public void handleGpsStatusChanged(Integer count) {
        if (mLastGpsSatelliteCount == count) {
            return;
        }
    	Message msg = Message.obtain(null, MSG_GPS_STATUS_UPDATE);
        msg.arg1 = count;
        sendClientMessage(msg);
        mLastGpsSatelliteCount = count;
        satellitesCount = count;
    }

    public void postLocationPackage(Object locations, Object packageNum) {
//    	Remote-checkpoint
        // if (config.isDebugging()) {
        // 	try {
	    //     	JSONObject json = new JSONObject();
	    //     	json.put("createdBy", config.getHttpHeaders().get("user-name"));
	    //     	json.put("content", locations.toString());
	    //     	json.put("checkpointName", "BeforeSendLocationPackage");
	    //     	json.put("type", "info");
	    //     	postRemoteLog(json);
        // 	} catch (JSONException e) {
        //         Log.e(TAG, e.toString());
		//     }
        // }

        PostLocationTask task = new LocationService.PostLocationTask();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
//            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, locations, packageNum);
        	task.execute(locations, packageNum);
        }
        else {
            task.execute(locations, packageNum);
        }
    }

    public String postLocationPackageAgain(Object locations, Object packageNum, Object packageId) {
    	Log.w(TAG, "postLocationPackageAgain");
    	String response = null;
//        try {
//		    response = HttpPostService.postGpsData(config.getUrl(), locations, packageNum, config.getHttpHeaders(), LOCATION_PACKAGE_COMPRESSION_METHOD_LZ64);

//		} catch (IOException e) {
//		    Log.e(TAG, e.toString());
            //        	Remote-checkpoint
            // if (config.isDebugging()) {
            // 	try {
    	    //     	JSONObject json = new JSONObject();
    	    //     	json.put("createdBy", config.getHttpHeaders().get("user-name"));
    	    //     	json.put("content", e.toString());
    	    //     	json.put("checkpointName", "PostLocationPackageAgain");
    	    //     	json.put("type", response == null ? "error" : "info");
    	    //     	postRemoteLog(json);
            // 	} catch (JSONException je) {
            //         Log.e(TAG, je.toString());
    		//     }
            // }
//		}

        return response;
    }

    public void postRemoteLog(Object logObj) {
//    	Toast.makeText(this, "uri:" + config.getHttpHeaders().get("remote"), Toast.LENGTH_SHORT).show();
    	PostRemoteLogTask task = new LocationService.PostRemoteLogTask();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
//            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logObj);
        	task.execute(logObj);
        }
        else {
            task.execute(logObj);
        }
    }

    public void persistLocation (BackgroundLocation location) {
        if (dao.persistLocation(location) > -1L) {
            Log.d(TAG, "Persisted Location: " + location.toString());
        } else {
            Log.w(TAG, "Failed to persist location");
        }
    }

    // public void postLocation(BackgroundLocation location) {
    // 	BackgroundLocation[] locations = new BackgroundLocation[10];
    //     PostLocationTask task = new LocationService.PostLocationTask();
    //     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    //         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, locations);
    //     }
    //     else {
    //         task.execute(location);
    //     }
    // }

    /**
     * Forces the main activity to re-launch if it's unloaded.
     */
    private void forceMainActivityReload() {
        PackageManager pm = getPackageManager();
        Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
        startActivity(launchIntent);
    }

    public Config getConfig() {
        return this.config;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    protected void sendClientMessage(Message msg) {
        for (int i = mClients.size() - 1; i >= 0; i--) {
            try {
                mClients.get(i).send(msg);
            } catch (Exception e) {
                // The client is dead.  Remove it from the list;
                // we are going through the list from back to front
                // so this is safe to do inside the loop.
//            	Remote-checkpoint
            	// if (config.isDebugging()) {
                // 	try {
        	    //     	JSONObject json = new JSONObject();
        	    //     	json.put("createdBy", config.getHttpHeaders().get("user-name"));
        	    //     	json.put("content", msg.getData().toString());
        	    //     	json.put("checkpointName", "sendClientMessage");
        	    //     	json.put("type", "error");
        	    //     	json.put("errorMsg", e.toString());
        	    //     	postRemoteLog(json);
                // 	} catch (JSONException je) {
                //         Log.e(TAG, je.toString());
        		//     }
                // }
                mClients.remove(i);
            }
        }
    }

    protected boolean isNetworkAvailable() {
        ConnectivityManager cm =
                (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (activeNetwork == null) {
        	Log.e(TAG, "###activeNetwork is null");
        	return false;
        }

        // Log.e(TAG, "###" + activeNetwork.isAvailable() + activeNetwork.isConnectedOrConnecting());
        return activeNetwork.isAvailable() && activeNetwork.isConnectedOrConnecting();
    }

    protected void notifyNetworkIssue() {
        Message msg = Message.obtain(null, MSG_NETWORK_STATUS_UPDATE);
        msg.arg1 = NETWORK_ERROR;
        sendClientMessage(msg);
        Log.e(TAG, "###sending network status");
        return;
    }

    protected void configNotification(Boolean stop, String contentTitle, String contentText) {
    	int startId = mStatus.optInt("serviceStartId");

    	if (stop) {
    		stopForeground(stop);
    		return;
    	}

    	if (config.getStartForeground()) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            String id = "my_channel_01";
            CharSequence name = "channal";
            String description = "channal";
            int importance = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel mChannel = new NotificationChannel(id, name, importance);
            mChannel.setDescription(description);
            mChannel.enableLights(true);
            mChannel.setLightColor(Color.RED);
            mChannel.enableVibration(true);
            mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            mNotificationManager.createNotificationChannel(mChannel);
          }
            // Build a Notification required for running service in foreground.
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
            builder.setContentTitle(contentTitle == null ? config.getNotificationTitle() : contentTitle);
            contentText = config.getNotificationText() + "High";
            builder.setContentText(contentText == null ? config.getNotificationText() : contentText);
            if (config.getSmallNotificationIcon() != null) {
                builder.setSmallIcon(getPluginResource(config.getSmallNotificationIcon()));
            } else {
                builder.setSmallIcon(R.mipmap.icon);
            }
            if (config.getLargeNotificationIcon() != null) {
                builder.setLargeIcon(BitmapFactory.decodeResource(getApplication().getResources(), getPluginResource(config.getLargeNotificationIcon())));
            }
            if (config.getNotificationIconColor() != null) {
                builder.setColor(this.parseNotificationIconColor(config.getNotificationIconColor()));
            }
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	            builder.setOngoing(true);
	            builder.setChannelId("my_channel_01");
			}

            setClickEvent(builder);

            Notification notification = builder.build();
            notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_NO_CLEAR;

            startForeground(startId, notification);
        }
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        switch (level) {
          case Activity.TRIM_MEMORY_UI_HIDDEN:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_UI_HIDDEN");
            break;
            case Activity.TRIM_MEMORY_RUNNING_MODERATE:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_RUNNING_MODERATE");
            break;
            case Activity.TRIM_MEMORY_RUNNING_LOW:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_RUNNING_LOW");
            break;
            case Activity.TRIM_MEMORY_RUNNING_CRITICAL:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_RUNNING_CRITICAL");
            break;
            case Activity.TRIM_MEMORY_BACKGROUND:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_BACKGROUND");
            break;
            case Activity.TRIM_MEMORY_MODERATE:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_MODERATE");
            break;
            case Activity.TRIM_MEMORY_COMPLETE:
            Log.d(TAG, "onTrimMemory() level=TRIM_MEMORY_COMPLETE");
            break;
        }
    }

    private class PostLocationTask extends AsyncTask<Object, Integer, String> {
    	protected void persistLocationPackage (String packagePayload, Integer packageNum) {
            LocationPackage lp = new LocationPackage(packagePayload, packageNum);
            Long id = lpDao.persistLocationWithLimit(lp, DB_MAX_ROW);
            if ( id > -1L) {
                Log.d(TAG, "Persisted location package: " + id);
            } else {
                Log.w(TAG, "Failed to persist location package.");
            }
        }

        @Override
        protected String doInBackground(Object... params) {
            Log.d(TAG, "Executing PostLocationTask#doInBackground");
          ArrayList<ArrayList<String>> locations = (ArrayList<ArrayList<String>>) params[0];
            JSONObject gpsData = new JSONObject();
            try {
              gpsData.put("TripId", LocationService.tripId);
               JSONArray jsonLocations = new JSONArray();
              JSONObject locationObject = null;
              for (ArrayList<String> location : locations) {
//                try {
                    locationObject = new JSONObject();
                    locationObject.put("DTime", location.get(0));
                    locationObject.put("LAT", location.get(1));
                    locationObject.put("LON", location.get(2));
                    locationObject.put("Speed", location.get(3));
                    locationObject.put("Altitude", location.get(4));
                    jsonLocations.put(locationObject);
              }
              gpsData.put("TripData", jsonLocations);
            } catch (JSONException e) {
              return null;
//                    return false;
//                }
//            }
            }
            Log.d(TAG, "Posting json to url: " + config.getUrl() + " headers: " + config.getHttpHeaders());

            String response = null;
            String compressedLocationPackage = LZString.compressToBase64(params[0].toString());
            try {
				response = HttpPostService.postGpsData(config.getUrl(), gpsData, config.getHttpHeaders(), LOCATION_PACKAGE_COMPRESSION_METHOD_LZ64);

			} catch (Exception e) {
				Log.e(TAG, e.toString());
				persistLocationPackage(compressedLocationPackage, (Integer) params[1]);
			}

            // Failed package will be persisted
            if (response == null)
                persistLocationPackage(compressedLocationPackage, (Integer) params[1]);

            return response;
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG, "PostLocationTask#onPostExecture");
//        	Remote-checkpoint
            // if (config.isDebugging()) {
            // 	try {
    	    //     	JSONObject json = new JSONObject();
    	    //     	json.put("createdBy", config.getHttpHeaders().get("user-name"));
    	    //     	json.put("content", result);
    	    //     	json.put("checkpointName", "AfterSendLocationPackage");
    	    //     	json.put("type", result == null ? "error" : "info");
    	    //     	postRemoteLog(json);
            // 	} catch (JSONException e) {
            //         Log.e(TAG, e.toString());
    		//     }
            // }

            if (result != null) {
            	if ( mTripStartTime == 0L )
                    return;
                try {
                    mStatus.put(KEY_TRIP_RESPONSE, result);
                    long duration = System.currentTimeMillis() - mTripStartTime;
                    mStatus.put(KEY_TRIP_DURATION, duration);
                    Message msg = Message.obtain(null, MSG_STATUS_UPDATE);
                    Bundle bundle  = new Bundle();
                    bundle.putString("msg", mStatus.toString());
                    msg.setData(bundle);
                    sendClientMessage(msg);

//                    /** send trip status message to ui */
//                    Message statusMsg = Message.obtain(null, MSG_TRIP_STATUS);
//                    Bundle statusBundle  = new Bundle();
//                    statusBundle.putString("msg", mStatus.toString());
//                    statusMsg.setData(statusBundle);
//                    sendClientMessage(statusMsg);
                } catch (JSONException e) {
				    Log.e(TAG, e.toString());
			    }
            }
        }
    }

    private class PostStartTripTask extends AsyncTask<String, Integer, String> {

        @Override
        protected String doInBackground(String... strStartBundles) {
            Log.d(TAG, "Executing PostStartTripTask#doInBackground");
            String response = null;
            try {
                JSONObject startBundle = new JSONObject(strStartBundles[0]);
                Log.d(TAG, "Posting json to url: " + config.getUrl() + " headers: " + config.getHttpHeaders());

                response = HttpPostService.postStartTrip(config.getUrl(), startBundle, config.getHttpHeaders());

            } catch (JSONException e) {
                Log.e(TAG, "Couldn't parse bundle from String to JSONObject");
            } catch (NullPointerException e) {
                Log.e(TAG, "Null pointer exception");
            } catch (IOException e) {
            	Log.e(TAG, e.toString());
			}

            return response;
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG, "PostStartTripTask#onPostExecture");
            try {
                Message msg = Message.obtain(null, MSG_START_TRIP_RESPONSE);
                Bundle bundle  = new Bundle();
                bundle.putString("msg", result);
                msg.setData(bundle);
                if (result == null) {
                	msg.arg1 = NETWORK_ERROR;
                }
                sendClientMessage(msg);
                LocationService.onTrip = true;
            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }
        }
    }

    private class PostStopTripTask extends AsyncTask<String, Integer, String> {

        @Override
        protected String doInBackground(String... strStopBundles) {
            Log.d(TAG, "Executing PostStopTripTask#doInBackground");

            String response = null;
            try {
                JSONObject stopBundle = new JSONObject(strStopBundles[0]);
                response = HttpPostService.postStopTrip(config.getUrl(), stopBundle, config.getHttpHeaders());

            } catch (IOException e) {
            	Log.e(TAG, e.toString());
			} catch (JSONException e) {
              e.printStackTrace();
			}

            return response;
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG, "PostStopTripTask#onPostExecture");
            Message msg = Message.obtain(null, MSG_STOP_TRIP_RESPONSE);
            Bundle bundle  = new Bundle();
            bundle.putString("msg", result);
            msg.setData(bundle);
            if (result == null) {
            	msg.arg1 = NETWORK_ERROR;
            }
            sendClientMessage(msg);
            LocationService.tripId = 0;
            LocationService.onTrip = false;
        }
    }

    private class PostRemoteLogTask extends AsyncTask<Object, Integer, String> {

        @Override
        protected String doInBackground(Object... params) {
            Log.d(TAG, "Executing PostRemoteLog#doInBackground");

            String response = null;
            try {
                response = HttpPostService.postRemoteLog(config.getHttpHeaders().get("remote").toString(), params[0], null);

            } catch (IOException e) {
            	Log.e(TAG, e.toString());
			}

            return response;
        }

        @Override
        protected void onPostExecute(String result) {

        }
    }
}
